// The libMesh Finite Element Library.
// Copyright (C) 2002-2021 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner

// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#include "libmesh/getpot.h"

#include "laplace_system.h"

#include "libmesh/dof_map.h"
#include "libmesh/fe_base.h"
#include "libmesh/fe_interface.h"
#include "libmesh/fem_context.h"
#include "libmesh/mesh.h"
#include "libmesh/quadrature.h"
#include "libmesh/string_to_enum.h"


// Bring in everything from the libMesh namespace
using namespace libMesh;

LaplaceSystem::LaplaceSystem(EquationSystems & es,
                             const std::string & name_in,
                             const unsigned int number_in) :
  FEMSystem(es, name_in, number_in)
{}

void LaplaceSystem::init_data ()
{
  // Check the input file for Reynolds number, application type,
  // approximation type
  GetPot infile("laplace.in");

  // Add the solution variable
  u_var = this->add_variable ("u", FIRST, LAGRANGE_VEC);

  // The solution is evolving, with a first order time derivative
  this->time_evolving(u_var, 1);

  // Useful debugging options
  // Set verify_analytic_jacobians to 1e-6 to use
  this->verify_analytic_jacobians = infile("verify_analytic_jacobians", 0.);
  this->print_jacobians = infile("print_jacobians", false);
  this->print_element_jacobians = infile("print_element_jacobians", false);

  this->init_dirichlet_bcs();

  // Do the parent's initialization after variables and boundary constraints are defined
  FEMSystem::init_data();
}

void LaplaceSystem::init_dirichlet_bcs()
{
#ifdef LIBMESH_ENABLE_DIRICHLET
  const boundary_id_type all_ids[6] = {0, 1, 2, 3, 4, 5};
  std::set<boundary_id_type> boundary_ids(all_ids, all_ids+6);

  std::vector<unsigned int> vars;
  vars.push_back(u_var);

  // Note that for vector-valued variables, it is assumed each
  // component is stored contiguously.  For 2-D elements in 3-D space,
  // only two components should be returned.
  SolutionFunction func(u_var);

  // In general, when reusing a system-indexed exact solution, we want
  // to use the default system-ordering constructor for
  // DirichletBoundary, so we demonstrate that here.  In this case,
  // though, we have only one (vector-valued!) variable, so system-
  // and local- orderings are the same.
  this->get_dof_map().add_dirichlet_boundary
    (libMesh::DirichletBoundary(boundary_ids, vars, func));
#endif // LIBMESH_ENABLE_DIRICHLET
}

void LaplaceSystem::init_context(DiffContext & context)
{
  FEMContext & c = cast_ref<FEMContext &>(context);

  // Get finite element object
  FEGenericBase<RealGradient> * fe;
  c.get_element_fe<RealGradient>(u_var, fe);

  // We should prerequest all the data
  // we will need to build the linear system.
  fe->get_JxW();
  fe->get_phi();
  fe->get_dphi();
  fe->get_xyz();

  FEGenericBase<RealGradient> * side_fe;
  c.get_side_fe<RealGradient>(u_var, side_fe);

  side_fe->get_JxW();
  side_fe->get_phi();
}


bool LaplaceSystem::element_time_derivative (bool request_jacobian,
                                             DiffContext & context)
{
  FEMContext & c = cast_ref<FEMContext &>(context);

  // Get finite element object
  FEGenericBase<RealGradient> * fe = nullptr;
  c.get_element_fe<RealGradient>(u_var, fe);

  // First we get some references to cell-specific data that
  // will be used to assemble the linear system.

  // Element Jacobian * quadrature weights for interior integration
  const std::vector<Real> & JxW = fe->get_JxW();

  // The velocity shape functions at interior quadrature points.
  const std::vector<std::vector<RealGradient>> & phi = fe->get_phi();

  // The velocity shape function gradients at interior
  // quadrature points.
  const std::vector<std::vector<RealTensor>> & grad_phi = fe->get_dphi();

  const std::vector<Point> & qpoint = fe->get_xyz();

  // The number of local degrees of freedom in each variable
  const unsigned int n_u_dofs = c.n_dof_indices(u_var);

  DenseSubMatrix<Number> & Kuu = c.get_elem_jacobian(u_var, u_var);

  DenseSubVector<Number> & Fu = c.get_elem_residual(u_var);

  // Now we will build the element Jacobian and residual.
  // Constructing the residual requires the solution and its
  // gradient from the previous timestep.  This must be
  // calculated at each quadrature point by summing the
  // solution degree-of-freedom values by the appropriate
  // weight functions.
  const unsigned int n_qpoints = c.get_element_qrule().n_points();

  for (unsigned int qp=0; qp != n_qpoints; qp++)
    {
      Tensor grad_u;

      c.interior_gradient(u_var, qp, grad_u);

      // Value of the forcing function at this quadrature point
      RealGradient f = this->forcing(qpoint[qp]);

      // First, an i-loop over the velocity degrees of freedom.
      // We know that n_u_dofs == n_v_dofs so we can compute contributions
      // for both at the same time.
      for (unsigned int i=0; i != n_u_dofs; i++)
        {
          Fu(i) += (grad_u.contract(grad_phi[i][qp]) - f*phi[i][qp])*JxW[qp];

          // Matrix contributions for the uu and vv couplings.
          if (request_jacobian)
            for (unsigned int j=0; j != n_u_dofs; j++)
              Kuu(i,j) += grad_phi[j][qp].contract(grad_phi[i][qp])*JxW[qp];
        }
    } // end of the quadrature point qp-loop

  return request_jacobian;
}



// bool LaplaceSystem::side_constraint (bool request_jacobian,
//                                      DiffContext & context)
// {
//   FEMContext & c = cast_ref<FEMContext &>(context);
//
//   // Get finite element object
//   FEGenericBase<RealGradient> * side_fe = nullptr;
//   c.get_side_fe<RealGradient>(u_var, side_fe);
//
//   // First we get some references to cell-specific data that
//   // will be used to assemble the linear system.
//
//   // Element Jacobian * quadrature weights for interior integration
//   const std::vector<Real> & JxW = side_fe->get_JxW();
//
//   // The velocity shape functions at interior quadrature points.
//   const std::vector<std::vector<RealGradient>> & phi = side_fe->get_phi();
//
//   // The number of local degrees of freedom in each variable
//   const unsigned int n_u_dofs = c.dof_indices_var[u_var].size();
//
//   const std::vector<Point> & qpoint = side_fe->get_xyz();
//
//   // The penalty value.  \frac{1}{\epsilon}
//   // in the discussion above.
//   const Real penalty = 1.e10;
//
//   DenseSubMatrix<Number> & Kuu = *c.elem_subjacobians[u_var][u_var];
//   DenseSubVector<Number> & Fu = *c.elem_subresiduals[u_var];
//
//   const unsigned int n_qpoints = (c.get_side_qrule())->n_points();
//
//   for (unsigned int qp=0; qp != n_qpoints; qp++)
//     {
//       Gradient u;
//       c.side_value(u_var, qp, u);
//
//       Gradient u_exact(this->exact_solution(0, qpoint[qp](0), qpoint[qp](1)),
//                        this->exact_solution(1, qpoint[qp](0), qpoint[qp](1)));
//
//       for (unsigned int i=0; i != n_u_dofs; i++)
//         {
//           Fu(i) += penalty*(u - u_exact)*phi[i][qp]*JxW[qp];
//
//           if (request_jacobian)
//             for (unsigned int j=0; j != n_u_dofs; j++)
//               Kuu(i,j) += penalty*phi[j][qp]*phi[i][qp]*JxW[qp];
//         }
//     }
//
//   return request_jacobian;
// }



RealGradient LaplaceSystem::forcing(const Point & p)
{
  Real x = p(0);
  Real y = p(1);
  Real z = p(2);

  const Real eps = 1.e-3;

  const Real fx = -(exact_solution(0, x, y, z-eps) +
                    exact_solution(0, x, y, z+eps) +
                    exact_solution(0, x, y-eps, z) +
                    exact_solution(0, x, y+eps, z) +
                    exact_solution(0, x-eps, y, z) +
                    exact_solution(0, x+eps, y, z) -
                    6.*exact_solution(0, x, y, z))/eps/eps;

  const Real fy = -(exact_solution(1, x, y, z-eps) +
                    exact_solution(1, x, y, z+eps) +
                    exact_solution(1, x, y-eps, z) +
                    exact_solution(1, x, y+eps, z) +
                    exact_solution(1, x-eps, y, z) +
                    exact_solution(1, x+eps, y, z) -
                    6.*exact_solution(1, x, y, z))/eps/eps;

  const Real fz = -(exact_solution(2, x, y, z-eps) +
                    exact_solution(2, x, y, z+eps) +
                    exact_solution(2, x, y-eps, z) +
                    exact_solution(2, x, y+eps, z) +
                    exact_solution(2, x-eps, y, z) +
                    exact_solution(2, x+eps, y, z) -
                    6.*exact_solution(2, x, y, z))/eps/eps;

  return RealGradient(fx, fy, fz);
}
